package com.smile.frame.workflow.flowable.demo.testFlowable;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.smile.frame.workflow.flowable.demo.constants.*;
import com.smile.frame.workflow.flowable.demo.dto.*;
import com.smile.frame.workflow.flowable.demo.service.BpmTaskService;
import com.smile.frame.workflow.flowable.demo.util.BpmnModelUtils;
import com.smile.frame.workflow.flowable.demo.util.FlowableUtils;
import com.smile.frame.workflow.flowable.demo.util.ValidationUtils;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.StartEvent;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ModelQuery;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Comment;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import java.util.*;


/**
 * 测试自定义用户、部门、角色 审批
 *
 * @author LGC
 */
@Slf4j
@SpringBootTest
class WorkflowFlowableApplicationTest3 {

    /**
     * 创建模型-> 模型部署（生成流程定义）-> 启动流程 -> 查询流程实例 ->查询任务 ->执行任务
     */
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private IdentityService identityService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private HistoryService historyService;
    @Autowired
    private BpmTaskService bpmTaskService;


    private void saveModelBpmnXml(Model model, String bpmnXml) {
        if (StrUtil.isEmpty(bpmnXml)) {
            return;
        }
        repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml));
    }

    private BpmModelMetaInfoRespDTO buildMetaInfo(Model model) {
        return JSONUtil.toBean(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
    }

    private void copyToCreateModel(Model model, BpmModelCreateReqDTO bean) {
        model.setName(bean.getName());
        model.setKey(bean.getKey());
        model.setCategory(bean.getCategory());
        model.setMetaInfo(buildMetaInfoStr(null, bean.getDescription()));
    }

    private void copyToUpdateModel(Model model, BpmModelUpdateReqDTO bean) {
        model.setName(bean.getName());
        model.setCategory(bean.getCategory());
        model.setMetaInfo(buildMetaInfoStr(buildMetaInfo(model), bean.getDescription()));
    }

    private String buildMetaInfoStr(BpmModelMetaInfoRespDTO metaInfo, String description) {
        if (metaInfo == null) {
            metaInfo = new BpmModelMetaInfoRespDTO();
        }
        if (StrUtil.isNotEmpty(description)) {
            metaInfo.setDescription(description);
        }

        return JSONUtil.toJsonStr(metaInfo);
    }


    private void validateBpmnXml(byte[] bpmnBytes) {
        BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes);
        if (bpmnModel == null) {
            log.error("流程模型不存在");
            throw new RuntimeException("流程模型不存在");
        }
        // 1. 没有 StartEvent
        StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel);
        if (startEvent == null) {
            log.error("部署流程失败，原因：BPMN 流程图中，没有开始事件");
            throw new RuntimeException("部署流程失败，原因：BPMN 流程图中，没有开始事件");
        }
        // 2. 校验 UserTask 的 name 都配置了
        List<UserTask> userTasks = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
        userTasks.forEach(userTask -> {
            if (StrUtil.isEmpty(userTask.getName())) {
                log.error(String.format("部署流程失败，原因：BPMN 流程图中，用户任务(%s)的名字不存在", userTask.getId()));
                throw new RuntimeException(String.format("部署流程失败，原因：BPMN 流程图中，用户任务(%s)的名字不存在", userTask.getId()));
            }
        });
    }


    /**
     * 挂起 deploymentId 对应的流程定义
     * <p>
     * 注意：这里一个 deploymentId 只关联一个流程定义
     *
     * @param deploymentId 流程发布Id
     */
    private void updateProcessDefinitionSuspended(String deploymentId) {
        if (StrUtil.isEmpty(deploymentId)) {
            return;
        }
        ProcessDefinition oldDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult();
        if (oldDefinition == null) {
            return;
        }
        updateProcessDefinitionState(oldDefinition.getId(), SuspensionState.SUSPENDED.getStateCode());
    }

    private void updateProcessDefinitionState(String id, Integer state) {
        // 激活
        if (Objects.equals(SuspensionState.ACTIVE.getStateCode(), state)) {
            repositoryService.activateProcessDefinitionById(id, false, null);
            return;
        }
        // 挂起
        if (Objects.equals(SuspensionState.SUSPENDED.getStateCode(), state)) {
            // suspendProcessInstances = false，进行中的任务，不进行挂起。
            // 原因：只要新的流程不允许发起即可，老流程继续可以执行。
            repositoryService.suspendProcessDefinitionById(id, false, null);
            return;
        }
        log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state);
    }


    private String createProcessDefinition(Model model, byte[] bpmnBytes) {
        // 创建 Deployment 部署
        log.info("创建 Deployment 部署 并创建实例");
        Deployment deploy = repositoryService.createDeployment()
                .key(model.getKey()).name(model.getName()).category(model.getCategory())
                .addBytes(model.getKey() + BpmnModelConstants.BPMN_FILE_SUFFIX, bpmnBytes)
                .tenantId("")
                .disableSchemaValidation() // 禁用 XML Schema 验证，因为有自定义的属性
                .deploy();

        log.info("设置 ProcessDefinition 的 category 分类");
        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId()).singleResult();
        // 设置 ProcessDefinition 的 category 分类
        repositoryService.setProcessDefinitionCategory(definition.getId(), model.getCategory());
        // 注意 1，ProcessDefinition 的 key 和 name 是通过 BPMN 中的 <bpmn2:process /> 的 id 和 name 决定
        // 注意 2，目前该项目的设计上，需要保证 Model、Deployment、ProcessDefinition 使用相同的 key，保证关联性。
        //          否则，会导致 ProcessDefinition 的分页无法查询到。
        if (!Objects.equals(definition.getKey(), model.getKey())) {
//            流程定义的key期望是({})，当前是({})，请修改 BPMN 流程图
//            throw exception(PROCESS_DEFINITION_KEY_NOT_MATCH, model.getKey(), definition.getKey());
        }
        if (!Objects.equals(definition.getName(), model.getName())) {
//            流程定义的name期望是({})，当前是({})，请修改 BPMN 流程图
//            throw exception(PROCESS_DEFINITION_NAME_NOT_MATCH, model.getName(), definition.getName());
        }

        // 插入拓展表
//        BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class)
//                .setModelId(model.getId()).setProcessDefinitionId(definition.getId());
//        if (form != null) {
//            definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf());
//        }
//        processDefinitionMapper.insert(definitionDO);
        return definition.getId();
    }


    /**
     * 支持租户
     * 0.各种校验，是否流程标识是否存在等
     * 1.创建新的模型
     * 2.保存模型的bpmnxml
     *
     * @param createReqVO
     * @return
     */
    public String createModel(BpmModelCreateReqDTO createReqVO, String bpmnXml) {
        log.info("start创建模型");
        if (!ValidationUtils.isXmlNCName(createReqVO.getKey())) {
            log.error("流程标识格式不正确，需要以字母或下划线开头，后接任意字母、数字、中划线、下划线、句点！");
            return null;
        }
        Model keyModel = repositoryService.createModelQuery().modelKey(createReqVO.getKey()).singleResult();
        if (keyModel != null) {
            log.error("校验流程标识已经存在");
            return null;
        }
        // 创建流程定义
        Model model = repositoryService.newModel();
        copyToCreateModel(model, createReqVO);
        // 租户
        model.setTenantId("");
        // 保存流程定义
        repositoryService.saveModel(model);
        // 保存 BPMN XML
        saveModelBpmnXml(model, bpmnXml);
        String modelId = model.getId();
        log.info("end创建模型:{}", modelId);
        return modelId;
    }

    public void updateModel(BpmModelUpdateReqDTO updateReqVO) {
        log.info("start更新模型");
        // 校验流程模型存在
        Model model = repositoryService.getModel(updateReqVO.getId());
        if (model == null) {
            log.error("流程模型不存在");
            throw new RuntimeException("流程模型不存在");
        }
        // 修改流程定义
        copyToUpdateModel(model, updateReqVO);
        // 更新模型
        repositoryService.saveModel(model);
        // 更新 BPMN XML
        saveModelBpmnXml(model, updateReqVO.getBpmnXml());
        log.info("end更新模型");
    }


    /**
     * 0.各种校验，bpmn等校验
     * 1.部署新的流程模型定义
     * 2.将旧部署的流程定义给挂起
     *
     * @param modelId
     */
    public void deployModel(String modelId) {
        log.info("start部署模型");
        // 1.1 校验流程模型存在
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtils.isEmpty(model)) {
            log.error("流程模型不存在");
            return;
        }
        // 1.2 校验流程图
        byte[] bpmnBytes = repositoryService.getModelEditorSource(model.getId());
        validateBpmnXml(bpmnBytes);
        // 1.3 校验表单已配
//        BpmModelMetaInfoRespDTO metaInfo = JSONUtil.toBean(model.getMetaInfo(), BpmModelMetaInfoRespDTO.class);
//        BpmFormDO form = validateFormConfig(metaInfo);
        // 1.4 校验任务分配规则已配置
//        taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
//        validateBpmnConfig(bpmnBytes);

        // 2.1 创建流程定义
        log.info("创建流程定义");
//        String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, form);
        String definitionId = createProcessDefinition(model, bpmnBytes);

        // 2.2 将老的流程定义进行挂起。也就是说，只有最新部署的流程定义，才可以发起任务。
        log.info("将老的流程定义进行挂起。也就是说，只有最新部署的流程定义，才可以发起任务。");
        updateProcessDefinitionSuspended(model.getDeploymentId());

        // 2.3 更新 model 的 deploymentId，进行关联
        log.info("更新 model 的 deploymentId，进行关联");
        ProcessDefinition definition = repositoryService.getProcessDefinition(definitionId);
        model.setDeploymentId(definition.getDeploymentId());
        repositoryService.saveModel(model);
        log.info("end部署模型");
    }

    /**
     * 1.删除模型
     * 2.将流程定义挂起
     *
     * @param modelId
     */
    public void deleteModel(String modelId) {
        // 校验流程模型存在
        Model model = repositoryService.getModel(modelId);
        if (ObjectUtils.isEmpty(model)) {
            log.error("流程模型不存在");
            return;
        }
        // 执行删除
        log.info("执行删除");
        repositoryService.deleteModel(modelId);
        // 挂起流程定义
        log.info("挂起流程定义");
        updateProcessDefinitionSuspended(model.getDeploymentId());
    }


    /**
     * 分页查询流程模型
     */
    private void queryModelPage(String tenantId, String key, String name, String category, Integer pageNo, Integer pageSize) {
        ModelQuery modelQuery = repositoryService.createModelQuery();
        if (StrUtil.isNotBlank(tenantId)) {
            modelQuery.modelTenantId(tenantId);
        }
        if (StrUtil.isNotBlank(key)) {
            modelQuery.modelKey(key);
        }
        if (StrUtil.isNotBlank(name)) {
            modelQuery.modelNameLike("%" + name + "%"); // 模糊匹配
        }
        if (StrUtil.isNotBlank(category)) {
            modelQuery.modelCategory(category);
        }

        // 执行查询
        long count = modelQuery.count();
        if (count == 0) {
            log.info("分页查询流程模型:null");
            return;
        }
        List<Model> models = modelQuery
                .orderByCreateTime().desc()
                .listPage((pageNo - 1) * pageSize, pageSize);
        log.info("分页查询流程模型:{}", JSONUtil.toJsonStr(models));
    }


    /**
     * 发起流程 通过key
     *
     * @param tenantId    租户ID
     * @param userId      发起人
     * @param key         流程key
     * @param businessKey 业务key 根据需要可不传
     * @param variables   流程变量
     */
    private void startProcessByKey(String tenantId, String userId, String key, String businessKey, Map<String, Object> variables) {
        // 设置流程发起人 可以web过滤器进行处理，而不用在这里手动处理
        if (StrUtil.isNotBlank(userId)) {
//            identityService.setAuthenticatedUserId(userId);
            Authentication.setAuthenticatedUserId(userId);
        }
//        runtimeService.startProcessInstanceById()
        ProcessInstance instance = runtimeService.startProcessInstanceByKeyAndTenantId(key, businessKey, variables, tenantId);
        log.info("流程实例ID:{},流程定义ID:{},业务标识:{}", instance.getId(), instance.getProcessDefinitionId(), instance.getBusinessKey());
        Authentication.setAuthenticatedUserId(null);
    }


    /**
     * 查询流程实例
     *
     * @param tenantId            租户ID
     * @param userId              发起人
     * @param processInstanceName 流程实例名称
     * @param processDefinitionId 流程定义ID
     * @param businessKey         业务key
     * @param category
     * @param status              BpmProcessInstanceStatusEnum
     * @param startTime
     * @param endTime
     * @param pageNo
     * @param pageSize
     */
    private void queryProcessInstance(String tenantId, String userId, String processInstanceName,
                                      String processDefinitionId, String businessKey,
                                      String category, Integer status,
                                      Date startTime, Date endTime, Integer pageNo, Integer pageSize) {
        HistoricProcessInstanceQuery processInstanceQuery = historyService.createHistoricProcessInstanceQuery()
                .includeProcessVariables()// 左关联ACT_HI_VARINST历史流程变量
                .orderByProcessInstanceStartTime().desc();
        if (StrUtil.isNotBlank(tenantId)) {
            processInstanceQuery.processInstanceTenantId(tenantId);
        }
        // 发起人
        if (StrUtil.isNotBlank(userId)) {
            processInstanceQuery.startedBy(userId);
        }
        if (StrUtil.isNotBlank(processInstanceName)) {
            processInstanceQuery.processInstanceNameLike("%" + processInstanceName + "%");
        }
        if (StrUtil.isNotBlank(processDefinitionId)) {
            processInstanceQuery.processDefinitionId("%" + processDefinitionId + "%");
        }
        if (StrUtil.isNotBlank(businessKey)) {
            processInstanceQuery.processInstanceBusinessKey(businessKey);
        }
        if (StrUtil.isNotBlank(category)) {
            processInstanceQuery.processDefinitionCategory(category);
        }
        // 流程实例变量自定义状态查询
        if (status != null) {
            processInstanceQuery.variableValueEquals(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, status);
        }
        if (ObjectUtil.isNotNull(startTime) && ObjectUtil.isNotNull(endTime)) {
            // yyyy-MM-dd HH:mm:ss
            processInstanceQuery.startedAfter(startTime);
            processInstanceQuery.startedBefore(endTime);
        }
        // 查询数量
        long processInstanceCount = processInstanceQuery.count();
        if (processInstanceCount == 0) {
            log.info("分页查询流程实例:null");
            return;
        }
        // 查询列表
        List<HistoricProcessInstance> processInstances = processInstanceQuery.listPage((pageNo - 1) * pageSize, pageSize);
        log.info("分页查询流程实例:{}", JSONUtil.toJsonStr(processInstances));


    }


    /**
     * 查询我的待办任务列表
     *
     * @param userId      处理人
     * @param businessKey 业务key
     * @return 待办任务列表
     */
    private List<Task> queryMyTodoTask(String userId, String deptId, String roleId, String positionId, String userGroupId, String businessKey) {
        // 查询待办事项列表
        TaskQuery taskQuery = taskService.createTaskQuery()
                .active() // 未挂起状态
                .includeProcessVariables()
                .orderByTaskCreateTime().desc();

        if (StrUtil.isNotBlank(userId)) {
            taskQuery.taskCandidateOrAssigned(userId);
        }
        //部门id dept:1、角色id role:1、职务id position:1 、用户组 userGroup:1
        List<String> candidateGroups = new ArrayList<>();
        if (StrUtil.isNotBlank(deptId)) {
            candidateGroups.add(deptId);
        }
        if (StrUtil.isNotBlank(roleId)) {
            candidateGroups.add(roleId);
        }
        if (StrUtil.isNotBlank(positionId)) {
            candidateGroups.add(positionId);
        }
        if (StrUtil.isNotBlank(userGroupId)) {
            candidateGroups.add(userGroupId);
        }
        if (candidateGroups.size() > 0) {
            taskQuery.taskCandidateGroupIn(candidateGroups);
        }
        // 业务key
        if (StrUtil.isNotBlank(businessKey)) {
            taskQuery.processInstanceBusinessKey(businessKey);
        }

        List<Task> list = taskQuery.list();
        log.info("我的待办任务列表:{}", JSONUtil.toJsonStr(list));
        return list;
    }

    /**
     * 查询我的已办任务列表
     *
     * @param userId      处理人
     * @param businessKey 业务key
     * @return 已办任务列表
     */
    private List<HistoricTaskInstance> queryMyFinishTask(String userId, String businessKey) {
        HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
                .finished() // 已完成
                .taskAssignee(userId) // 分配给自己
                .includeTaskLocalVariables()
                .orderByHistoricTaskInstanceEndTime().desc(); // 审批时间倒序


        // 业务key
        if (StrUtil.isNotBlank(businessKey)) {
            historicTaskInstanceQuery.processInstanceBusinessKey(businessKey);
        }
        List<HistoricTaskInstance> list = historicTaskInstanceQuery.list();
        log.info("我的已办任务列表:{}", JSONUtil.toJsonStr(list));
        return list;
    }

    /**
     * 测试创建模型
     */
    @Test
    public void createModel() {
        BpmModelCreateReqDTO reqVO = new BpmModelCreateReqDTO();
        reqVO.setKey("test3");
        reqVO.setName("测试自定义用户、部门、角色 审批");
        reqVO.setCategory("测试");
        reqVO.setDescription("测试自定义用户、部门、角色 审批");
        String bpmnXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<bpmn2:definitions\n" +
                "        xmlns:flowable=\"http://flowable.org/bpmn\"\n" +
                "        xmlns:bpmn2=\"http://www.omg.org/spec/BPMN/20100524/MODEL\"\n" +
                "        xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\"\n" +
                "        xmlns:dc=\"http://www.omg.org/spec/DD/20100524/DC\" xmlns:di=\"http://www.omg.org/spec/DD/20100524/DI\"\n" +
                "        id=\"diagram_Process_1719813018803\" targetNamespace=\"http://flowable.org/bpmn\">\n" +
                "    <bpmn2:process id=\"test3\" name=\"测试自定义用户、部门、角色 审批\" isExecutable=\"true\">\n" +
                "        <bpmn2:startEvent id=\"Event_start1\" name=\"开始\">\n" +
                "            <bpmn2:outgoing>Flow_1qijlx1</bpmn2:outgoing>\n" +
                "        </bpmn2:startEvent>\n" +
                "        <bpmn2:userTask id=\"deptTask1\" name=\"指定部门审批\" flowable:candidateGroups=\"dept:1,dept:2\">\n" +
                "            <bpmn2:incoming>Flow_1qijlx1</bpmn2:incoming>\n" +
                "            <bpmn2:outgoing>Flow_0k1j90n</bpmn2:outgoing>\n" +
                "        </bpmn2:userTask>\n" +
                "        <bpmn2:sequenceFlow id=\"Flow_1qijlx1\" sourceRef=\"Event_start1\" targetRef=\"deptTask1\"/>\n" +
                "        <bpmn2:userTask id=\"userTask1\" name=\"指定用户审批\" flowable:candidateUsers=\"1,2\">\n" +
                "            <bpmn2:incoming>Flow_0k1j90n</bpmn2:incoming>\n" +
                "            <bpmn2:outgoing>Flow_1mxru1o</bpmn2:outgoing>s\n" +
                "        </bpmn2:userTask>\n" +
                "        <bpmn2:sequenceFlow id=\"Flow_0k1j90n\" sourceRef=\"deptTask1\" targetRef=\"userTask1\"/>\n" +
                "        <bpmn2:userTask id=\"roleTask1\" name=\"指定角色审批\" flowable:candidateGroups=\"role:1\">\n" +
                "            <bpmn2:incoming>Flow_1mxru1o</bpmn2:incoming>\n" +
                "            <bpmn2:outgoing>Flow_end</bpmn2:outgoing>\n" +
                "        </bpmn2:userTask>\n" +
                "        <bpmn2:sequenceFlow id=\"Flow_1mxru1o\" sourceRef=\"userTask1\" targetRef=\"roleTask1\"/>\n" +
                "        <bpmn2:endEvent id=\"Event_end1\" name=\"结束\">\n" +
                "            <bpmn2:incoming>Flow_end</bpmn2:incoming>\n" +
                "        </bpmn2:endEvent>\n" +
                "        <bpmn2:sequenceFlow id=\"Flow_end\" sourceRef=\"roleTask1\" targetRef=\"Event_end1\"/>\n" +
                "    </bpmn2:process>\n" +
                "    <bpmndi:BPMNDiagram id=\"BPMNDiagram_1\">\n" +
                "        <bpmndi:BPMNPlane id=\"BPMNPlane_1\" bpmnElement=\"Process_1719813018803\">\n" +
                "            <bpmndi:BPMNEdge id=\"Flow_1qijlx1_di\" bpmnElement=\"Flow_1qijlx1\">\n" +
                "                <di:waypoint x=\"308\" y=\"280\"/>\n" +
                "                <di:waypoint x=\"360\" y=\"280\"/>\n" +
                "            </bpmndi:BPMNEdge>\n" +
                "            <bpmndi:BPMNEdge id=\"Flow_0k1j90n_di\" bpmnElement=\"Flow_0k1j90n\">\n" +
                "                <di:waypoint x=\"460\" y=\"280\"/>\n" +
                "                <di:waypoint x=\"520\" y=\"280\"/>\n" +
                "            </bpmndi:BPMNEdge>\n" +
                "            <bpmndi:BPMNEdge id=\"Flow_1mxru1o_di\" bpmnElement=\"Flow_1mxru1o\">\n" +
                "                <di:waypoint x=\"620\" y=\"280\"/>\n" +
                "                <di:waypoint x=\"680\" y=\"280\"/>\n" +
                "            </bpmndi:BPMNEdge>\n" +
                "            <bpmndi:BPMNEdge id=\"Flow_end_di\" bpmnElement=\"Flow_end\">\n" +
                "                <di:waypoint x=\"780\" y=\"280\"/>\n" +
                "                <di:waypoint x=\"842\" y=\"280\"/>\n" +
                "            </bpmndi:BPMNEdge>\n" +
                "            <bpmndi:BPMNShape id=\"Event_start1_di\" bpmnElement=\"Event_start1\">\n" +
                "                <dc:Bounds x=\"272\" y=\"262\" width=\"36\" height=\"36\"/>\n" +
                "                <bpmndi:BPMNLabel>\n" +
                "                    <dc:Bounds x=\"279\" y=\"305\" width=\"22\" height=\"14\"/>\n" +
                "                </bpmndi:BPMNLabel>\n" +
                "            </bpmndi:BPMNShape>\n" +
                "            <bpmndi:BPMNShape id=\"deptTask1_di\" bpmnElement=\"deptTask1\">\n" +
                "                <dc:Bounds x=\"360\" y=\"240\" width=\"100\" height=\"80\"/>\n" +
                "            </bpmndi:BPMNShape>\n" +
                "            <bpmndi:BPMNShape id=\"userTask1_di\" bpmnElement=\"userTask1\">\n" +
                "                <dc:Bounds x=\"520\" y=\"240\" width=\"100\" height=\"80\"/>\n" +
                "            </bpmndi:BPMNShape>\n" +
                "            <bpmndi:BPMNShape id=\"roleTask1_di\" bpmnElement=\"roleTask1\">\n" +
                "                <dc:Bounds x=\"680\" y=\"240\" width=\"100\" height=\"80\"/>\n" +
                "            </bpmndi:BPMNShape>\n" +
                "            <bpmndi:BPMNShape id=\"Event_end1_di\" bpmnElement=\"Event_end1\">\n" +
                "                <dc:Bounds x=\"842\" y=\"262\" width=\"36\" height=\"36\"/>\n" +
                "                <bpmndi:BPMNLabel>\n" +
                "                    <dc:Bounds x=\"849\" y=\"305\" width=\"22\" height=\"14\"/>\n" +
                "                </bpmndi:BPMNLabel>\n" +
                "            </bpmndi:BPMNShape>\n" +
                "        </bpmndi:BPMNPlane>\n" +
                "    </bpmndi:BPMNDiagram>\n" +
                "</bpmn2:definitions>";
        String modelId = createModel(reqVO, bpmnXml);
        deployModel(modelId);
    }


    /**
     * 测试部署模型
     */
    @Test
    public void deployModelTest() {
        deployModel("ac83ca1f-3518-11ef-a40e-00ff0e3fbc3b");
    }

    /**
     * 测试删除模型
     */
    @Test
    public void deleteModelTest() {
        deleteModel("e79c0340-33a0-11ef-aa02-00ff0e3fbc3b");
    }

    /**
     * 测试分页查询模型
     */
    @Test
    public void queryModelPageTest() {
        queryModelPage(null, "test3", null, "测试", 1, 10);
    }

    /**
     * 测试发起流程实例
     */
    @Test
    public void startProcessInstanceTest() {
        String tenantId = null;
        String userId = "1234";
        String processDefinitionKey = "test3";
        String businessKey = "10004";
        // 参数
        Map<String, Object> variables = new HashMap<>();
        // 自定义流程变量 流程实例状态：审批中
        variables.put(BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS, BpmProcessInstanceStatusEnum.RUNNING.getStatus());
        startProcessByKey(tenantId, userId, processDefinitionKey, businessKey, variables);
    }


    /**
     * 测试查询流程实例
     */
    @Test
    public void queryProcessInstanceTest() {
        String tenantId = null;
        String userId = "123"; // 发起人
        String processInstanceName = null;
        String processDefinitionId = null;
        String businessKey = null;
        String category = null;
        Integer status = null; //审批中
//        Integer status = BpmProcessInstanceStatusEnum.RUNNING.getStatus(); //审批中
        Date startTime = DateUtil.parse("2024-06-27 09:00:00", "yyyy-MM-dd HH:mm:ss");
        Date endTime = DateUtil.parse("2024-07-30 23:00:00", "yyyy-MM-dd HH:mm:ss");
        Integer pageNo = 1;
        Integer pageSize = 10;
        queryProcessInstance(tenantId, userId, processInstanceName, processDefinitionId, businessKey,
                category, status, startTime, endTime, pageNo, pageSize);

    }

    /**
     * 测试查询我的待办任务列表
     */
    @Test
    public void queryMyTodoTaskTest() {
        queryMyTodoTask("", "1", "", "", "", "10004");
    }


    /**
     * 测试查询我的已完成任务列表
     */
    @Test
    public void queryMyFinishTaskTest() {
        queryMyFinishTask("1", "10003");
    }


    /**
     * 测试 审核通过 我的待办任务
     */
    @Test
    public void approvalTaskTest() {
        bpmTaskService.approvalTask("1", "4db69d08-38ee-11ef-bcaa-00ff0e3fbc3b", null, "审核通过，没问题");
    }

    /**
     * 测试 审核不通过 我的待办任务
     */
    @Test
    public void rejectTaskTest() {
        log.info("任务审核不通过start");
        Integer status = BpmTaskStatusEnum.REJECT.getStatus(); // 审核不通过
        String userId = "3"; // 谁的
        String taskId = "0a50ec56-381c-11ef-99b5-00ff0e3fbc3b";
        String businessKey = "10002"; // 业务key 唯一的
        String reason = "审核不通过。没原因"; // 审批意见
        Map<String, Object> variables = null; //变量实例（动态表单）

        // 当前用户上下文
        Authentication.setAuthenticatedUserId(userId);

        // 1.1根据任务ID查询出任务，并判断是否属于我的任务
        Task task = taskService.createTaskQuery().taskId(taskId).includeTaskLocalVariables().singleResult();
        if (task == null) {
            log.error("该任务不存在");
            return;
        }
        if (task.getAssignee() != null && !Objects.equals(task.getAssignee(), userId)) {
            log.error("该任务的审批人不是你");
            return;
        }
        // 1.2 校验任务实例
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                .includeProcessVariables()
                .processInstanceId(task.getProcessInstanceId())
                .singleResult();
        if (processInstance == null) {
            log.error("流程实例不存在id:{}", task.getProcessInstanceId());
            return;
        }
        String processInstanceId = processInstance.getId();
        // 1.3 设置任务本地变量 审核状态 和 原因
        taskService.setVariableLocal(task.getId(), BpmConstants.TASK_VARIABLE_STATUS, status);
        taskService.setVariableLocal(task.getId(), BpmConstants.TASK_VARIABLE_REASON, reason);
        // 1.4 添加评论
        taskService.addComment(task.getId(), processInstanceId,
                BpmCommentTypeEnum.REJECT.getType(), BpmCommentTypeEnum.REJECT.formatComment(reason));
        // 1.5 任务声明 进行任务代理人指定（效果类似于setAssignee方法）
        taskService.claim(task.getId(), userId);
        // 1.6 去完成任务
        if (CollUtil.isNotEmpty(variables)) {
            variables = FlowableUtils.filterTaskFormVariable(variables);
            taskService.complete(task.getId(), variables, true);
        } else {
            taskService.complete(task.getId());
        }


        // 1.5 更新流程实例，审批不通过！
        // 1.5.1 更新流程实例 status
        runtimeService.setVariable(processInstanceId, BpmConstants.PROCESS_INSTANCE_VARIABLE_STATUS,
                BpmProcessInstanceStatusEnum.REJECT.getStatus());
        // 1.5.2 删除流程实例，以实现驳回任务时，取消整个审批流程
        runtimeService.deleteProcessInstance(processInstanceId, StrUtil.format(BpmDeleteReasonEnum.REJECT_TASK.format(reason)));
        // 1.5.3 可发送审核不通过事件或通知...
        log.info("任务审核不通过end");
    }


    /**
     * 委托任务
     */
    @Test
    public void delegateTask() {
        bpmTaskService.delegateTask("1", "4db69d08-38ee-11ef-bcaa-00ff0e3fbc3b", "2", "无法决定委托给2进行处理");
    }

    /**
     * 转办任务
     */
    @Test
    public void transferTask() {
        bpmTaskService.transferTask("1", "4db69d08-38ee-11ef-bcaa-00ff0e3fbc3b", "3", "我处理不了转办给3进行处理");
    }

    /**
     * 获取当前任务的可回退的任务集合
     */
    @Test
    public void getUserTaskListByReturn() {
        bpmTaskService.getUserTaskListByReturn("1", "d4b41f6b-3b3e-11ef-b282-00ff0e3fbc3b");
    }


    /**
     * 查询审批记录
     */
    @Test
    public void queryHistoryTaskByProcessInstanceId() {
        log.info("根据流程实例查询历史任务（其实就是审批进度）start");
        String processInstanceId = "4daf22f1-38ee-11ef-bcaa-00ff0e3fbc3b";
        List<HistoricTaskInstance> historyTasks = historyService.createHistoricTaskInstanceQuery()
                .includeTaskLocalVariables()// 查询出任务本地变量
                .processInstanceId(processInstanceId)
                .orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
                .list();
        log.info("实例任务:{}", JSONUtil.toJsonStr(historyTasks));
        List<Comment> taskComments = taskService.getProcessInstanceComments(processInstanceId);
        log.info("实例评论：{}", JSONUtil.toJsonStr(taskComments));
        for (HistoricTaskInstance historyTask : historyTasks) {
            // 任务状态变量
            Object reason = historyTask.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_REASON);
            Object status = historyTask.getTaskLocalVariables().get(BpmConstants.TASK_VARIABLE_STATUS);
            log.info("任务状态变量::{}", status + " :" + reason);

            // 任务所匹配评论
            for (Comment taskComment : taskComments) {
                if (taskComment.getTaskId().equals(taskComment.getTaskId())) {
                    log.info("任务所匹配评论{}", JSONUtil.toJsonStr(taskComment));
                }
            }
            //select * from ACT_HI_COMMENT where TASK_ID_ = ? and TYPE_ = 'comment' order by TIME_ desc
            // 默认类型为comment，我们插入指定了类型所以查询出来应该没有
            List<Comment> taskComments1 = taskService.getTaskComments(historyTask.getId());
            log.info("任务评论：{}", JSONUtil.toJsonStr(taskComments1));

        }
        log.info("根据流程实例查询历史任务（其实就是审批进度）end");
    }

    @Test
    public void queryHistoryActivity() {
        log.info("根据流程实例查询历史活动start");
        String processInstanceId = "2fccb9a0-3811-11ef-bcbf-00ff0e3fbc3b";
        // 查看历史
        List<HistoricActivityInstance> activities = historyService.createHistoricActivityInstanceQuery()
                .processInstanceId(processInstanceId)
                .finished()
                .orderByHistoricActivityInstanceEndTime().asc()
                .list();
        log.info("完成的历史活动：{}", JSONUtil.toJsonStr(activities));
        for (HistoricActivityInstance activity : activities) {
            log.info(activity.getActivityName());
        }
        log.info("根据流程实例查询历史活动end");
    }

}